/*
 * Command.cpp
 *
 *  Created on: 2012-12-27
 *      Author: yangentao@gmail.com
 */

#include <iostream>
#include <fstream>
#include <assert.h>
#include <boost/bind.hpp>
#include <boost/regex.hpp>
#include "Task.h"
#include "../yet/File.h"

using namespace std;
namespace ftp {
using namespace yet;

Task::Task() :
		Super() {
}

Task::Task(FtpContext* c) :
		Super(c) {
}
Task::~Task() {
}
bool Task::execute() {
	assert(packet->cmd != FTP_NONE);
	assert(context);
	context->OnWait = boost::bind(&Task::onWait, this, _1);
	context->dataTransfer.OnTransfer = boost::bind(&Task::onDataTransfer, this, _1);
	bool b = processCommand(packet.get());
	context->OnWait.clear();
	context->dataTransfer.OnTransfer.clear();
	return b;
}
Task& Task::connect(string host, u_short port) {
	context->host = host;
	context->port = port;
	packet.reset(new Packet());
	packet->CONNECT();
	return *this;
}

Task& Task::login(string username, string password, string account) {
	assert(!username.empty());
	packet.reset(new Packet());
	packet->USER(username);
	if (!password.empty()) {
		packet->nextPacket.reset(new Packet());
		packet->nextPacket->PASS(password);
		if (!account.empty()) {
			packet->nextPacket->nextPacket.reset(new Packet());
			packet->nextPacket->nextPacket->ACCT(account);
		}
	}
	context->username = username;
	context->password = password;
	context->account = account;
	return *this;
}
Task& Task::rename(string from, string to) {
	assert(!from.empty());
	assert(!to.empty());
	packet.reset(new Packet());
	packet->RNFR(from);
	packet->nextPacket.reset(new Packet());
	packet->nextPacket->RNTO(to);
	return *this;
}
Task& Task::rest(int offset) {
	assert(offset >=0);
	packet.reset(new Packet());
	packet->REST(tostring(offset));
	return *this;
}
Task& Task::acct(string account) {
	assert(!account.empty());
	packet.reset(new Packet());
	packet->ACCT(account);
	return *this;
}
Task& Task::cwd(string pathname) {
	packet.reset(new Packet());
	packet->CWD(pathname);
	return *this;
}
Task& Task::cdup() {
	packet.reset(new Packet());
	packet->CDUP();
	return *this;
}
Task& Task::smnt(string pathname) {
	packet.reset(new Packet());
	packet->SMNT(pathname);
	return *this;
}
Task& Task::quit() {
	packet.reset(new Packet());
	packet->QUIT();
	return *this;
}
Task& Task::rein() {
	packet.reset(new Packet());
	packet->REIN();
	return *this;
}
Task& Task::port(string host, short port) {
	context->usePasv = false;
	packet.reset(new Packet());
	packet->PORT(host, port);
	return *this;
}
Task& Task::pasv() {
	context->usePasv = true;
	packet.reset(new Packet());
	packet->PASV();
	return *this;
}
Task& Task::type(string type) {
	packet.reset(new Packet());
	packet->TYPE(type);
	return *this;
}
Task& Task::stru(string stru) {
	packet.reset(new Packet());
	packet->STRU(stru);
	return *this;
}
Task& Task::mode(string mode) {
	packet.reset(new Packet());
	packet->MODE(mode);
	return *this;
}
Task& Task::stor(string localfile, string remotefile) {
	localPath = localfile;
	packet.reset(new Packet());
	packet->STOR(remotefile);
	return *this;
}
Task& Task::stou(string localfile) {
	localPath = localfile;
	packet.reset(new Packet());
	packet->STOU();
	return *this;
}
Task& Task::appe(string localfile, string remotefile) {
	localPath = localfile;
	packet.reset(new Packet());
	packet->APPE(remotefile);
	return *this;
}
Task& Task::allo(string n, string n2) {
	packet.reset(new Packet());
	packet->ALLO(n, n2);
	return *this;
}
Task& Task::abor() {
	packet.reset(new Packet());
	packet->ABOR();
	return *this;
}
Task& Task::dele(string pathname) {
	packet.reset(new Packet());
	packet->DELE(pathname);
	return *this;
}
Task& Task::rmd(string pathname) {
	packet.reset(new Packet());
	packet->RMD(pathname);
	return *this;
}
Task& Task::mkd(string pathname) {
	packet.reset(new Packet());
	packet->MKD(pathname);
	return *this;
}
Task& Task::pwd() {
	packet.reset(new Packet());
	packet->PWD();
	return *this;
}
//type A
//pasv
//connect
//list
Task& Task::list(string pathname) {
	packet.reset(new Packet());
	packet->LIST(pathname);
	return *this;
}
Task& Task::nlst(string pathname) {
	packet.reset(new Packet());
	packet->NLST(pathname);
	return *this;
}
Task& Task::site(string s) {
	packet.reset(new Packet());
	packet->SITE(s);
	return *this;
}
Task& Task::syst() {
	packet.reset(new Packet());
	packet->SYST();
	return *this;
}
Task& Task::stat(string pathname) {
	packet.reset(new Packet());
	packet->STAT(pathname);
	return *this;
}
Task& Task::help(string s) {
	packet.reset(new Packet());
	packet->HELP(s);
	return *this;
}
Task& Task::noop() {
	packet.reset(new Packet());
	packet->NOOP();
	return *this;
}

Task& Task::retr(string remotefile, string localPath) {
	this->localPath = localPath;
	packet.reset(new Packet());
	packet->RETR(remotefile);
	return *this;
}

Task& Task::feat() {
	packet.reset(new Packet());
	packet->FEAT();
	return *this;
}
//OPTS UTF8 ON
Task& Task::opts(string cmd, string options) {
	packet.reset(new Packet());
	packet->OPTS(cmd, options);
	return *this;
}

bool Task::processCommand(Packet* p) {
	int cmd = p->cmd;
//	if(!inSet(cmd, FTP_CONNECT, FTP_USER, FTP_PASS, FTP_ACCT, FTP_HELP, FTP_RETR, FTP_STOR, FTP_NOOP)){
//		if(!context->isSupportCommand(cmd)){
//			ftplog<<"Server Not Support: "<<ftpCmdName(cmd)<<end;
//			return false;
//		}
//	}

	if (cmd == FTP_CONNECT) {
		if (context->connect()) {
			if (context->recvControl(p) && p->isOk()) {
				return true;
			}
			context->sock.close();
		}
		return false;
	}
	if (cmd == FTP_QUIT) {
		context->sendRecvControl(p);
		context->sock.close();
		return true;
	}
	if (p->cmd == FTP_SYST) {
		if (context->sendRecvControl(p) && p->isOk()) {
			string line = p->firstLine();
			context->systData = line.substr(4);
			return true;
		}
		return false;
	}
	if (cmd == FTP_PWD) {
		if (context->sendRecvControl(p) && p->isOk()) {
			string line = p->firstLine();
			boost::regex reg("\\d+\\s+\\\"([^\\\"]+)\\\"");
			boost::smatch mat;
			if (boost::regex_search(line, mat, reg)) {
				context->pwd = mat[1];
				return true;
			}
		}
		return false;
	}
	if (inSet(p->cmd, FTP_NLST, FTP_LIST, FTP_RETR, FTP_STOR, FTP_STOU, FTP_APPE)) {
		if (context->sendRecvControl(p) && p->isOk()) {
			context->dataTransfer.waitFinish();
			return context->dataTransfer.success;
		}
		return false;
	}
	if (cmd == FTP_HELP) {
		if (context->sendRecvControl(p) && p->isOk()) {
			context->serverCommands.clear();
			if (p->hasResponse()) {
				vector<string> lines = p->lastEcho.lines;
				if (lines.size() > 2) {
					for (size_t i = 1; i < lines.size() - 1; ++i) {
						string line = lines[i];
						vector<string> words = split(line, ' ');
						for (size_t w = 0; w < words.size(); ++w) {
							string word = trim(words[w]);
							if (!word.empty()) {
								context->serverCommands.insert(word);
							}
						}
					}
				}
			}
			return true;
		}
		return false;
	}
	if (cmd == FTP_PASV) {
		context->pasvIP.clear();
		context->pasvPort = 0;
		if (context->sendRecvControl(p) && p->isOk()) {
			string line = p->firstLine();
			boost::regex reg("\\((\\d+),(\\d+),(\\d+),(\\d+),(\\d+),(\\d+)\\)");
			boost::smatch mat;
			if (boost::regex_search(line, mat, reg)) {
				context->pasvIP = mat[1];
				context->pasvIP.append(".");
				context->pasvIP.append(mat[2]);
				context->pasvIP.append(".");
				context->pasvIP.append(mat[3]);
				context->pasvIP.append(".");
				context->pasvIP.append(mat[4]);
				context->pasvPort = toint(mat[5]) * 256 + toint(mat[6]);
				if (context->dataTransfer.dataSock.createTcpSocket()) {
					if (0 == context->dataTransfer.dataSock.connect(context->pasvIP, context->pasvPort)) {
						return true;
					} else {
						SocketError e;
						e.print();
					}
				}
			}
		}
		return false;
	}

	if (context->sendRecvControl(p)) {
		if (p->isOk()) {
			return true;
		} else if (p->isNext()) {
			if (p->nextPacket) {
				return processCommand(p->nextPacket.get());
			}
			return true;
		}
		return false;
	}
	return false;
}

//  REIN
//LIST, NLST,RETR, STOR,  STOU,APPE,
void Task::onWait(Packet* p) {
	switch (p->cmd) {
	case FTP_LIST:
	case FTP_NLST:
	case FTP_RETR: //fallthrough
	case FTP_STOR:
	case FTP_STOU:
	case FTP_APPE:
		context->dataTransfer.beginTrans(p);
		break;
	}
}
bool Task::onDataTransfer(void* arg) {
	Packet* p = (Packet*) arg;
	switch (p->cmd) {
	case FTP_RETR:
		return download(p);
	case FTP_STOR: //fall through
	case FTP_STOU:
	case FTP_APPE:
		return upload(p);
	case FTP_LIST:
		return list(p);
		break;
	case FTP_NLST:
		return nlst(p);
		break;
	}
	return false;
}

bool Task::list(Packet* p) {
	context->listDataOK = false;
	context->listData.clear();
	int n = 0;
	AutoBuffer<char> buf(256);
	while (true) {
		n = context->dataTransfer.dataSock.recv(buf.buffer, buf.length);
		if (n > 0) {
			context->listData.append(buf.buffer, n);
		} else if (n == 0) {
			context->listDataOK = true;
			context->dataTransfer.dataSock.close();
			return true;
		} else {
			context->listDataOK = false;
			SocketError e;
			ftplog << err << e.getCode() << " " << e.getCodeName() << end;
			context->dataTransfer.dataSock.close();
			break;
		}
	}
	return false;
}
bool Task::nlst(Packet* p) {
	context->nlstDataOK = false;
	context->nlstData.clear();
	int n = 0;
	AutoBuffer<char> buf(256);
	while (true) {
		n = context->dataTransfer.dataSock.recv(buf.buffer, buf.length);
		if (n > 0) {
			context->nlstData.append(buf.buffer, n);
		} else if (n == 0) {
			context->nlstDataOK = true;
			context->dataTransfer.dataSock.close();
			return true;
		} else {
			context->nlstDataOK = false;
			SocketError e;
			ftplog << err << e.getCode() << " " << e.getCodeName() << end;
			context->dataTransfer.dataSock.close();
			break;
		}
	}
	return false;
}

bool Task::upload(Packet* p) {
	FileReader reader;
	if (!reader.open(localPath)) {
		ftplog << err << "Open Local File Failed! " << localPath << end;
		return false;
	}
	int n = 0;
	AutoBuffer<char> buf(256);
	while (true) {
		n = reader.read(buf.buffer, buf.length);
		if (n > 0) {
			if (context->dataTransfer.dataSock.sendAll(buf.buffer, n)) {
				continue;
			} else {
				SocketError e;
				ftplog << err << e.getCode() << " " << e.getCodeName() << end;
				context->dataTransfer.dataSock.close();
				ftplog << err << "upload Failed!" << end;
				return false;
			}
		} else if (n == 0) {
			context->dataTransfer.dataSock.close();
			return true;
		} else {
			return false;
		}
	}
	return false;
}
bool Task::download(Packet* p) {
	ofstream file(localPath.c_str(), ios::binary);
	if (!file.good()) {
		ftplog << err << "Open Local File Failed! " << localPath << end;
		return false;
	}
	int n = 0;
	AutoBuffer<char> buf(256);
	while (true) {
		n = context->dataTransfer.dataSock.recv(buf.buffer, buf.length);
		if (n > 0) {
			file.write(buf.buffer, n);
		} else if (n == 0) {
			context->dataTransfer.dataSock.close();
			file.flush();
			file.close();
			ftplog << "Download Success" << end;
			return true;
			break;
		} else {
			SocketError e;
			ftplog << err << e.getCode() << " " << e.getCodeName() << end;
			context->dataTransfer.dataSock.close();
			ftplog << err << "Download Failed!" << end;
			break;
		}
	}
	return false;
}
} /* namespace yet */
